import { MAX_COLLAPSED_CODE_HEIGHT } from "@renderer/config/constant";
import { useCodeHighlight } from "@renderer/hooks/use-code-highlight";
import { useCodeStyle } from "@renderer/hooks/use-code-style";
import { cn } from "@renderer/lib/utils";
import { getReactStyleFromToken, uuid } from "@renderer/utils/shiki";
import logger from "@shared/logger/renderer-logger";
import { useVirtualizer } from "@tanstack/react-virtual";
import { debounce } from "es-toolkit";
import { Check, ChevronDown, ChevronUp, Copy } from "lucide-react";
import {
  memo,
  useCallback,
  useEffect,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
} from "react";
import { useTranslation } from "react-i18next";
import type { ThemedToken } from "shiki/core";

interface SyntaxHighlighterProps {
  children: string;
  language: string;
  showLineNumbers?: boolean;
  className?: string;
  title?: string;
  wordWrap?: boolean;
  showCollapseIcon?: boolean;
  defaultCollapsed?: boolean;
  collapseMode?: "full" | "height";
  collapsedHeight?: number;
  onToggleCollapsed?: (collapsed: boolean) => void;
  extraActions?: React.ReactNode;
  fontSize?: number;
  onHeightChange?: (scrollHeight: number) => void;
}

const SyntaxHighlighter = memo(
  ({
    children,
    language,
    showLineNumbers = false,
    className,
    title,
    wordWrap = false,
    showCollapseIcon = false,
    defaultCollapsed = false,
    collapseMode = "height",
    collapsedHeight = MAX_COLLAPSED_CODE_HEIGHT,
    onToggleCollapsed,
    extraActions,
    fontSize = 14,
    onHeightChange,
  }: SyntaxHighlighterProps) => {
    const [isCopied, setIsCopied] = useState(false);
    const [isCollapsed, setIsCollapsed] = useState<boolean>(defaultCollapsed);
    const { t } = useTranslation();
    const { getShikiPreProperties, isShikiThemeDark } = useCodeStyle();
    const shikiThemeRef = useRef<HTMLDivElement>(null);
    const scrollerRef = useRef<HTMLDivElement>(null);
    const callerId = useRef(`${Date.now()}-${uuid()}`).current;

    const rawLines = useMemo(
      () =>
        typeof children === "string" ? children.trimEnd().split("\n") : [],
      [children],
    );

    const gutterDigits = useMemo(
      () =>
        showLineNumbers ? Math.max(rawLines.length.toString().length, 1) : 0,
      [showLineNumbers, rawLines.length],
    );

    useLayoutEffect(() => {
      let mounted = true;
      getShikiPreProperties(language).then((properties) => {
        if (!mounted) return;
        const shikiTheme = shikiThemeRef.current;
        if (shikiTheme) {
          shikiTheme.className = `${properties.class || "shiki"} code-viewer ${className ?? ""}`;
          shikiTheme.classList.add(
            isShikiThemeDark ? "shiki-dark" : "shiki-light",
          );

          if (properties.style) {
            shikiTheme.style.cssText += `${properties.style}`;
          }
          if (properties.tabindex !== undefined) {
            shikiTheme.tabIndex = properties.tabindex;
          }
        }
      });
      return () => {
        mounted = false;
      };
    }, [getShikiPreProperties, isShikiThemeDark, className, language]);

    const getScrollElement = useCallback(() => scrollerRef.current, []);
    const getItemKey = useCallback(
      (index: number) => `${callerId}-${index}`,
      [callerId],
    );
    const estimateSize = useCallback(
      () => Math.round((fontSize - 1) * 1.6),
      [fontSize],
    );

    const virtualizer = useVirtualizer({
      count: rawLines.length,
      getScrollElement,
      getItemKey,
      estimateSize,
      overscan: 20,
    });

    const virtualItems = virtualizer.getVirtualItems();

    const { tokenLines, highlightLines } = useCodeHighlight({
      rawLines,
      language,
    });

    const debouncedHighlightLines = useMemo(
      () => debounce(highlightLines, 300),
      [highlightLines],
    );

    useEffect(() => {
      if (virtualItems.length > 0 && shikiThemeRef.current) {
        const lastIndex = virtualItems[virtualItems.length - 1].index;
        debouncedHighlightLines(lastIndex + 1);
      }
    }, [virtualItems, debouncedHighlightLines]);

    // Initial highlighting for complete content
    useEffect(() => {
      if (rawLines.length > 0 && shikiThemeRef.current) {
        // Trigger highlighting for all lines if content is complete
        highlightLines(rawLines.length);
      }
    }, [rawLines.length, highlightLines]);

    useLayoutEffect(() => {
      onHeightChange?.(scrollerRef.current?.scrollHeight ?? 0);
    }, [onHeightChange]);

    const handleCopy = async () => {
      try {
        await navigator.clipboard.writeText(children);
        setIsCopied(true);
        setTimeout(() => setIsCopied(false), 2000);
      } catch (error) {
        logger.warn("Failed to copy code", { error });
      }
    };

    const handleToggleCollapse = () => {
      const newValue = !isCollapsed;
      setIsCollapsed(newValue);
      onToggleCollapsed?.(newValue);
    };

    const contentContainerClass = cn(
      "relative border-inherit",
      isCollapsed
        ? collapseMode === "height"
          ? "flex-none overflow-auto"
          : "hidden"
        : "flex-1 overflow-auto",
    );

    return (
      <div className={cn("flex h-full w-full flex-col", className)}>
        {(title || showCollapseIcon || extraActions) && (
          <div className="flex shrink-0 items-center justify-between border-b bg-muted/50 px-3 py-2 font-medium text-foreground text-sm">
            <span>{title}</span>
            <div className="flex items-center gap-1.5">
              {extraActions}
              {showCollapseIcon && (
                <button
                  type="button"
                  onClick={handleToggleCollapse}
                  className={cn(
                    "flex items-center justify-center rounded-md p-1.5 transition-colors",
                    "hover:bg-muted-fg/10 focus-visible:bg-muted-fg/10 focus-visible:outline-none",
                    "text-muted-fg hover:text-foreground",
                  )}
                  title={
                    isCollapsed ? t("common.expand") : t("common.collapse")
                  }
                >
                  {isCollapsed ? (
                    <ChevronDown className="h-3.5 w-3.5" />
                  ) : (
                    <ChevronUp className="h-3.5 w-3.5" />
                  )}
                </button>
              )}
              <button
                type="button"
                onClick={handleCopy}
                className={cn(
                  "flex items-center justify-center rounded-md p-1.5 transition-colors",
                  "hover:bg-muted-fg/10 focus-visible:bg-muted-fg/10 focus-visible:outline-none",
                  isCopied
                    ? "text-green-600 dark:text-green-400"
                    : "text-muted-fg hover:text-foreground",
                )}
                title={
                  isCopied
                    ? t("common.copied-success")
                    : t("common.copy-to-clipboard")
                }
              >
                {isCopied ? (
                  <Check className="zoom-in-75 h-3.5 w-3.5 animate-in transition-all duration-200" />
                ) : (
                  <Copy className="h-3.5 w-3.5" />
                )}
              </button>
            </div>
          </div>
        )}
        <div ref={shikiThemeRef}>
          <div
            ref={scrollerRef}
            className={cn(
              "shiki-scroller block overflow-x-auto",
              contentContainerClass,
              isShikiThemeDark ? "shiki-dark" : "shiki-light",
            )}
            style={{
              fontSize: `${fontSize - 1}px`,
              maxHeight:
                isCollapsed && collapseMode === "height"
                  ? collapsedHeight
                  : undefined,
              overflowY:
                isCollapsed && collapseMode === "height" ? "auto" : "hidden",
              padding: "0.5rem 0 0.5rem 1rem",
              ...(gutterDigits > 0 &&
                ({
                  "--gutter-width": `${gutterDigits}ch`,
                } as React.CSSProperties)),
            }}
          >
            <div
              className="shiki-list relative w-full"
              style={{
                height: `${virtualizer.getTotalSize()}px`,
              }}
            >
              <div
                className="absolute top-0 left-0 w-full"
                style={{
                  transform: `translateY(${virtualItems[0]?.start ?? 0}px)`,
                }}
              >
                {virtualItems.map((virtualItem) => (
                  <div
                    key={virtualItem.key}
                    data-index={virtualItem.index}
                    ref={virtualizer.measureElement}
                  >
                    <VirtualizedRow
                      rawLine={rawLines[virtualItem.index]}
                      tokenLine={tokenLines[virtualItem.index]}
                      showLineNumbers={showLineNumbers}
                      index={virtualItem.index}
                      lineHeight={estimateSize()}
                      wordWrap={wordWrap}
                    />
                  </div>
                ))}
              </div>
            </div>
          </div>
        </div>
      </div>
    );
  },
);

SyntaxHighlighter.displayName = "SyntaxHighlighter";

const plainTokenStyle = {
  color: "inherit",
  bgColor: "inherit",
  htmlStyle: {
    opacity: "0.35",
  },
};

interface VirtualizedRowData {
  rawLine: string;
  tokenLine?: ThemedToken[];
  showLineNumbers: boolean;
  lineHeight: number;
  wordWrap: boolean;
}

const VirtualizedRow = memo(
  ({
    rawLine,
    tokenLine,
    showLineNumbers,
    index,
    lineHeight,
    wordWrap,
  }: VirtualizedRowData & { index: number }) => {
    const progressiveTokenLine = useMemo(() => {
      const rawContent = rawLine || "";
      const highlightedTokens = tokenLine ?? [];
      const hasHighlighting = highlightedTokens.length > 0;

      if (!rawContent && !hasHighlighting) {
        return [
          {
            content: "\u00A0",
            offset: 0,
            ...plainTokenStyle,
          },
        ];
      }

      if (!hasHighlighting) {
        return [
          {
            content: rawContent || "\u00A0",
            offset: 0,
            ...plainTokenStyle,
          },
        ];
      }

      const highlightedContent = highlightedTokens
        .map((t) => t.content)
        .join("");

      if (highlightedContent === rawContent) {
        return highlightedTokens.map((token) => ({
          ...token,
          content: token.content || "\u00A0",
        }));
      }

      if (rawContent && highlightedTokens.length > 0) {
        if (highlightedTokens.length === 1) {
          return [
            {
              ...highlightedTokens[0],
              content: rawContent,
              offset: 0,
            },
          ];
        }

        const result: ThemedToken[] = [];
        let consumedLength = 0;

        for (
          let i = 0;
          i < highlightedTokens.length && consumedLength < rawContent.length;
          i++
        ) {
          const token = highlightedTokens[i];
          const tokenLength = token.content?.length || 0;
          const remainingLength = rawContent.length - consumedLength;
          const actualLength = Math.min(tokenLength, remainingLength);

          if (actualLength > 0) {
            result.push({
              ...token,
              content: rawContent.slice(
                consumedLength,
                consumedLength + actualLength,
              ),
              offset: consumedLength,
            });
            consumedLength += actualLength;
          }
        }

        if (consumedLength < rawContent.length) {
          result.push({
            content: rawContent.slice(consumedLength),
            offset: consumedLength,
            ...plainTokenStyle,
          });
        }

        return result.length > 0
          ? result
          : [
              {
                content: rawContent,
                offset: 0,
                ...plainTokenStyle,
              },
            ];
      }

      return [
        {
          content: rawContent || "\u00A0",
          offset: 0,
          ...plainTokenStyle,
        },
      ];
    }, [rawLine, tokenLine]);

    return (
      <div
        className={cn(
          "line flex w-full items-start font-mono",
          wordWrap ? "whitespace-pre-wrap break-words" : "whitespace-pre",
        )}
        style={{
          lineHeight: `${lineHeight}px`,
        }}
      >
        {showLineNumbers && (
          <span
            className="line-number mr-4 flex-shrink-0 select-none overflow-hidden text-right font-mono tabular-nums opacity-35"
            style={{
              width: "var(--gutter-width, 1.2ch)",
            }}
          >
            {index + 1}
          </span>
        )}
        <span className="line-content relative flex-1 pr-4">
          {progressiveTokenLine.map(
            (token: ThemedToken, tokenIndex: number) => (
              <span
                key={`${index}-${tokenIndex}-${token.offset ?? tokenIndex}`}
                className="transition-all duration-300 ease-out"
                style={getReactStyleFromToken(token)}
              >
                {token.content}
              </span>
            ),
          )}
        </span>
      </div>
    );
  },
);

VirtualizedRow.displayName = "VirtualizedRow";

export { SyntaxHighlighter };
